package fr.umlv.model;
import java.awt.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.swing.JOptionPane;

import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.Repository;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.bindings.CmisBindingFactory;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;

// TODO: Auto-generated Javadoc
/**
 * The Class ClientSession.
 */
public class ClientSession {

    /** The Constant WORKBENCH_PREFIX. */
    public static final String WORKBENCH_PREFIX = "cmis.workbench.";
    
    /** The Constant OBJECT_PREFIX. */
    public static final String OBJECT_PREFIX = WORKBENCH_PREFIX + "object.";
    
    /** The Constant FOLDER_PREFIX. */
    public static final String FOLDER_PREFIX = WORKBENCH_PREFIX + "folder.";
    
    /** The Constant ACCEPT_SELF_SIGNED_CERTIFICATES. */
    public static final String ACCEPT_SELF_SIGNED_CERTIFICATES = WORKBENCH_PREFIX + "acceptSelfSignedCertificates";
    
    /**
     * The Enum Authentication.
     */
    public enum Authentication {
        
        /** The NONE. */
        NONE, 
 
 /** The STANDARD. */
 STANDARD, 
 
 /** The NTLM. */
 NTLM
    }

    /** The Constant PROPERTY_SET. */
    private static final Set<String> PROPERTY_SET = new HashSet<String>();
    static {
        PROPERTY_SET.add(PropertyIds.OBJECT_ID);
        PROPERTY_SET.add(PropertyIds.OBJECT_TYPE_ID);
        PROPERTY_SET.add(PropertyIds.NAME);
        PROPERTY_SET.add(PropertyIds.CONTENT_STREAM_MIME_TYPE);
        PROPERTY_SET.add(PropertyIds.CONTENT_STREAM_LENGTH);
        PROPERTY_SET.add(PropertyIds.CONTENT_STREAM_FILE_NAME);
        PROPERTY_SET.add(PropertyIds.CREATED_BY);
        PROPERTY_SET.add(PropertyIds.CREATION_DATE);
        PROPERTY_SET.add(PropertyIds.LAST_MODIFIED_BY);
        PROPERTY_SET.add(PropertyIds.LAST_MODIFICATION_DATE);
    }

    /** The session parameters. */
    private Map<String, String> sessionParameters;
    
    /** The repositories. */
    private List<Repository> repositories;
    
    /** The session. */
    private Session session;
    
    /** The object operation context. */
    private OperationContext objectOperationContext;
    
    /** The folder operation context. */
    private OperationContext folderOperationContext;

    /**
     * Instantiates a new client session.
     *
     * @param sessionParameters the session parameters
     * @param compo the compo
     */
    public ClientSession(Map<String, String> sessionParameters, Component compo) {
        if (sessionParameters == null) {
            throw new IllegalArgumentException("Parameters must not be null!");
        }
        connect(sessionParameters, compo);
    }

    /**
     * Creates the session parameters.
     *
     * @param url the url
     * @param binding the binding
     * @param username the username
     * @param password the password
     * @param authentication the authentication
     * @return the map
     */
    public static Map<String, String> createSessionParameters(String url, BindingType binding, String username,
            String password, Authentication authentication) {
        Map<String, String> parameters = new LinkedHashMap<String, String>();

        if (binding == BindingType.WEBSERVICES) {
            parameters.put(SessionParameter.BINDING_TYPE, BindingType.WEBSERVICES.value());
            parameters.put(SessionParameter.WEBSERVICES_REPOSITORY_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_NAVIGATION_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_OBJECT_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_VERSIONING_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_DISCOVERY_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_MULTIFILING_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_RELATIONSHIP_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_ACL_SERVICE, url);
            parameters.put(SessionParameter.WEBSERVICES_POLICY_SERVICE, url);
            parameters.put(SessionParameter.OBJECT_FACTORY_CLASS, "org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl");
        } else {
            parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
            parameters.put(SessionParameter.ATOMPUB_URL, url);
            parameters.put(SessionParameter.OBJECT_FACTORY_CLASS, "org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl");
        }

        switch (authentication) {
        case STANDARD:
            parameters.put(SessionParameter.USER, username);
            parameters.put(SessionParameter.PASSWORD, password);
            break;
        case NTLM:
            parameters.put(SessionParameter.USER, username);
            parameters.put(SessionParameter.PASSWORD, password);
            parameters.put(SessionParameter.AUTHENTICATION_PROVIDER_CLASS,
                    CmisBindingFactory.NTLM_AUTHENTICATION_PROVIDER);
            break;
        }

        // get additional workbench properties from system properties
        Properties sysProps = System.getProperties();
        for (String key : sysProps.stringPropertyNames()) {
            if (key.startsWith(WORKBENCH_PREFIX)) {
                parameters.put(key, sysProps.getProperty(key));
            }
        }
        
        parameters.put(SessionParameter.OBJECT_FACTORY_CLASS, "org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl");

        return parameters;
    }

    /**
     * Connect.
     *
     * @param sessionParameters the session parameters
     * @param compo the compo
     */
    private void connect(Map<String, String> sessionParameters, Component compo) {
        this.sessionParameters = sessionParameters;

        // set a new dummy authenticator
        // don't send previous credentials to another server
        Authenticator.setDefault(new Authenticator() {
        });

        if (Boolean.parseBoolean(sessionParameters.get(ACCEPT_SELF_SIGNED_CERTIFICATES))) {
            acceptSelfSignedCertificates();
        }
        
        String url = sessionParameters.get(SessionParameter.ATOMPUB_URL);
		if (url.startsWith("https")) {
			String path = url.replaceFirst("https://", "");
			String[] c = path.split(":");
			int port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
			path = path.replaceAll("/.*", "");
			try {
				checkCertificates(path, port, compo);
			} catch (KeyManagementException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (KeyStoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (NoSuchAlgorithmException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (CertificateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        repositories = SessionFactoryImpl.newInstance().getRepositories(sessionParameters);
    }

    /**
     * Gets the repositories.
     *
     * @return the repositories
     */
    public List<Repository> getRepositories() {
        return repositories;
    }

    /**
     * Creates the session.
     *
     * @param index the index
     * @return the session
     */
    public Session createSession(int index) {
        session = repositories.get(index).createSession();
        createOperationContexts();
        return getSession();
    }

    /**
     * Gets the session.
     *
     * @return the session
     */
    public Session getSession() {
        return session;
    }

    /**
     * Gets the session parameters.
     *
     * @return the session parameters
     */
    public Map<String, String> getSessionParameters() {
        return Collections.unmodifiableMap(sessionParameters);
    }

    /**
     * Gets the object operation context.
     *
     * @return the object operation context
     */
    public OperationContext getObjectOperationContext() {
        return objectOperationContext;
    }

    /**
     * Gets the folder operation context.
     *
     * @return the folder operation context
     */
    public OperationContext getFolderOperationContext() {
        return folderOperationContext;
    }

    /**
     * Creates the operation contexts.
     */
    private void createOperationContexts() {
        // object operation context
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.FILTER, "*");
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_ACLS, "true");
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_ALLOWABLE_ACTIONS, "true");
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_POLICIES, "true");
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_RELATIONSHIPS,
                IncludeRelationships.BOTH.value());
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.RENDITION_FILTER, "*");
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.ORDER_BY, null);
        setDefault(OBJECT_PREFIX, sessionParameters, ClientOperationContext.MAX_ITEMS_PER_PAGE, "1000");

        objectOperationContext = new ClientOperationContext(OBJECT_PREFIX, sessionParameters);

        // folder operation context
        if (!sessionParameters.containsKey(FOLDER_PREFIX + ClientOperationContext.FILTER)) {
            ObjectType type = session.getTypeDefinition(BaseTypeId.CMIS_DOCUMENT.value());

            StringBuilder filter = new StringBuilder();
            for (String propId : PROPERTY_SET) {
                PropertyDefinition<?> propDef = type.getPropertyDefinitions().get(propId);
                if (propDef != null) {
                    if (filter.length() > 0) {
                        filter.append(",");
                    }
                    filter.append(propDef.getQueryName());
                }
            }

            sessionParameters.put(FOLDER_PREFIX + ClientOperationContext.FILTER, filter.toString());
        }

        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_ACLS, "false");
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_ALLOWABLE_ACTIONS, "false");
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_POLICIES, "false");
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.INCLUDE_RELATIONSHIPS,
                IncludeRelationships.NONE.value());
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.RENDITION_FILTER, "cmis:none");
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.ORDER_BY, null);
        setDefault(FOLDER_PREFIX, sessionParameters, ClientOperationContext.MAX_ITEMS_PER_PAGE, "1000");

        folderOperationContext = new ClientOperationContext(FOLDER_PREFIX, sessionParameters);
    }

    /**
     * Sets the default.
     *
     * @param prefix the prefix
     * @param map the map
     * @param key the key
     * @param value the value
     */
    private void setDefault(String prefix, Map<String, String> map, String key, String value) {
        if (!map.containsKey(prefix + key)) {
            map.put(prefix + key, value);
        }
    }

    /**
     * Accept self signed certificates.
     */
    private void acceptSelfSignedCertificates() {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };

        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        } catch (Exception e) {
        }
    }
   
	/** The Constant HEXDIGITS. */
	private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
	
	/**
	 * To hex string.
	 *
	 * @param bytes the bytes
	 * @return the string
	 */
	private static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 3);
		for (int b : bytes) {
			b &= 0xff;
			sb.append(HEXDIGITS[b >> 4]);
			sb.append(HEXDIGITS[b & 15]);
			sb.append(' ');
		}
		return sb.toString();
	}

	/**
	 * Check certificates.
	 *
	 * @param host the host
	 * @param port the port
	 * @param compo the compo
	 * @throws KeyStoreException the key store exception
	 * @throws NoSuchAlgorithmException the no such algorithm exception
	 * @throws CertificateException the certificate exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws KeyManagementException the key management exception
	 */
	public static void checkCertificates(String host, int port, Component compo) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException {
		File file = new File("jssecacerts");
		if (file.isFile() == false) {
			char SEP = File.separatorChar;
			File dir = new File(System.getProperty("java.home") + SEP
					+ "lib" + SEP + "security");
			file = new File(dir, "jssecacerts");
			if (file.isFile() == false) {
				file = new File(dir, "cacerts");
			}
		}
		char[] passphrase = "changeit".toCharArray();
		InputStream in = new FileInputStream(file);
		KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
		ks.load(in, passphrase);
		in.close();

		SSLContext context = SSLContext.getInstance("SSL");
		TrustManagerFactory tmf =
			TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
		SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
		context.init(null, new TrustManager[] {tm}, new java.security.SecureRandom());
		SSLSocketFactory factory = context.getSocketFactory();
		
		SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
		socket.setSoTimeout(10000);
		try {
			socket.startHandshake();
			socket.close();
			HttpsURLConnection.setDefaultSSLSocketFactory(factory);
			return;
			
		} catch (SSLException e) {
			e.printStackTrace(System.out);
		}

		X509Certificate[] chain = tm.chain;
		if (chain == null) {
			JOptionPane.showMessageDialog(compo,
					"Could not obtain server certificate chain",
				    "Certificate error",
				    JOptionPane.ERROR_MESSAGE);
			return;
		}


		MessageDigest sha1 = MessageDigest.getInstance("SHA1");
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < chain.length; i++) {
			X509Certificate cert = chain[i];
			sb.append(" ").append(i + 1).append(" Subject ").append(cert.getSubjectDN()).append('\n');
			sb.append("   Issuer  ").append(cert.getIssuerDN()).append('\n');
			sha1.update(cert.getEncoded());
			sb.append("   sha1    ").append(toHexString(sha1.digest())).append('\n');
			md5.update(cert.getEncoded());
			sb.append("   md5     ").append(toHexString(md5.digest())).append('\n');
		}
		sb.append('\n').append("Enter (Yes) to add certificate to trusted keystore or (No) to quit");
		int k;
		int n = JOptionPane.showConfirmDialog(compo, sb.toString(), "Confirm certificate ?", JOptionPane.YES_NO_OPTION);
	    if (n == JOptionPane.YES_OPTION) {
	    	k=0;
	    } else {
	    	return;
	    }
	    
	    for (int i = 0; i < chain.length; i++) {
			X509Certificate cert = chain[k];
			String alias = host + "-" + (k + 1);
			ks.setCertificateEntry(alias, cert);
	    }
		OutputStream out = new FileOutputStream("jssecacerts");
		ks.store(out, passphrase);
		out.close();
		
		socket.close();
		checkCertificates(host, port, compo);
	}
	
	/**
	 * The Class SavingTrustManager.
	 */
	private static class SavingTrustManager implements X509TrustManager {
		
		/** The tm. */
		private final X509TrustManager tm;
		
		/** The chain. */
		private X509Certificate[] chain;

		/**
		 * Instantiates a new saving trust manager.
		 *
		 * @param tm the tm
		 */
		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		/* (non-Javadoc)
		 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
		 */
		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		/* (non-Javadoc)
		 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String)
		 */
		public void checkClientTrusted(X509Certificate[] chain, String authType)
		throws CertificateException {
			throw new UnsupportedOperationException();
		}

		/* (non-Javadoc)
		 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String)
		 */
		public void checkServerTrusted(X509Certificate[] chain, String authType)
		throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}
}
